package weeny.pre.face.treetable;

import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JTree;

import prefuse.Visualization;
import prefuse.data.*;
import prefuse.data.column.Column;
import prefuse.data.event.EventConstants;
import prefuse.data.event.TableListener;
import prefuse.data.event.TupleSetListener;
import prefuse.data.expression.Predicate;
import prefuse.data.parser.BooleanParser;
import prefuse.data.parser.DataParser;
import prefuse.data.parser.ObjectParser;
import prefuse.data.parser.StringParser;
import prefuse.data.tuple.CompositeTupleSet;
import prefuse.data.tuple.DefaultTupleSet;
import prefuse.data.tuple.TupleSet;
import prefuse.render.NullRenderer;
import prefuse.render.Renderer;
import prefuse.util.io.IOLib;
import prefuse.visual.VisualItem;
import weeny.pre.display.MoreVisualization;
import weeny.pre.event.GroupListener;
import weeny.pre.io.parser.ColorParser;
import weeny.pre.io.parser.EnumParser;
import weeny.util.io.ModUtil;
import weeny.util.logs.Debug;

/**
 * Provide a model to handle all tuples in a visualization as a {@link JTree}
 * structure. There are four levels in this structure. The top one is a VisNode
 * as root of the tree, and it is hidden. The second one is all groups in the
 * visualization, which are collected by {@link MoreVisualization#getGroups()}.
 * There are three types of groups: 1) {@link CompositeTupleSet}, such as 
 * {@link Graph} and {@link Tree}, which contains a edge table and a node table;
 * 2) {@link Table}s, which is just a single table; 3) Focus groups, which are
 * usually {@link DefaultTupleSet}s for holding any tuples. The first two are
 * data groups, and the third one is virtual. If you enable DnD, you can drag
 * and drop selected tuples on the a focus group to add those tuples to that
 * focus group. But you cannot drop tuples on data groups. Below group nodes,
 * are tuple nodes that represent all the tuples that belong to those groups.
 * And the bottom level is field. If you double click a tuple node, you can see
 * all the column names and values it contains. Of course, you can right click
 * on the table header to choose columns to show. By the way, the group nodes
 * also have those columns, which means that changing the values for the group
 * nodes will change every tuple in that group. The last thing is that if you
 * double click the field nodes, you will see a popup window, so you can change
 * the value in that field, because all the table columns for field nodes are
 * disabled. Of course, you can also show the target column and edit it in the
 * tuple node level.
 * 
 * The first column is used as {@link javax.swing.JTree} label. Please see 
 * {@link AbstractTreeNode#AbstractTreeNode(String, Object)}. Please notice that
 * it is possible that not all tuples have the same columns. Because you can add
 * tuples from different tables with different columns to the same focus group.
 * In that case, those cells will display {@link AbstractTreeNode#UNAVAILABLE},
 * and become uneditable.
 * 
 * @author weeny
 */

public class PreGroupModel extends AbstractPreModel{
	private MoreVisualization vis;
	
	@Override
	public Visualization getVisualization() {
		return vis;
	}
	public PreGroupModel(MoreVisualization moreVis, String... columns) {
		super(new VisNode(columns[0], moreVis), columns);
		vis = moreVis;
		GroupListener gListener = new GroupListener() {
			/* We need to monitor the added groups, if a group is added, it is
			 * either a focus one or a data one. When a tuple is added to any
			 * groups, we just ned to invalidate that group and repaint. But if
			 * a tuple is removed from a data group, then we need to check every
			 * group in the visualization and try to remove it from them too,
			 * because once the tuple is removed from a data group, it becomes
			 * invalided, and trying to access in the focus group will trigger
			 * exceptions! 
			 * 
			 * We also monitor every tuple updated in the data group, and update
			 * the jtable accordingly, because, the tuple values may be changed
			 * by some other functions.
			 */
			public void groupUpdated(String[] addedGroup, String[] removedOne) {
				final VisNode root = (VisNode) getRoot();
				root.children = null;
				root.getChildCount();
				for (int i = 0; addedGroup!=null&&i<addedGroup.length; i++){
					final String group = addedGroup[i];
					TupleSetListener tsl = new TupleSetListener() {
						public void tupleSetChanged(TupleSet tset, 
								Tuple[] addedTuple, Tuple[] removedTuple) {
							GroupNode node = null;
							for(AbstractTreeNode n : root.children){
								if (n.str.equals(group)) {
									node = (GroupNode) n;
									break;
								}
							}
							//if removed tuples are none, we just return
							if (removedTuple==null || removedTuple.length==0) {
								node.invalidateChildren();
								vis.repaint();
								return;
							}
							//if there are removed tuples we need to search
							//every focus group and remove it too.
							for(AbstractTreeNode n : root.children){
								TupleSet ts = vis.getFocusGroup(n.str);
								if (tset instanceof Table && ts != null) {
									for(Tuple rm : removedTuple){
										if (ts.removeTuple(rm)){
											((GroupNode)n).invalidateChildren();
											node.invalidateChildren();
										}
									}
								}
							}
							node.invalidateChildren();
							vis.repaint();
						}
					};//end of TupleSetListener
					vis.getGroup(group).addTupleSetListener(tsl);
					
					if (vis.getGroup(group) instanceof Table) {
						//if something is changed in the data, refresh the
						//table
						TableListener tl = new TableListener() {
							public void tableChanged(Table t, int start, 
									int end, int col, int type) {
								if(col != EventConstants.ALL_COLUMNS) {
									//adding/removing row events are already
									//handled by tuplesetlistener
									if (columnIndex(t.getColumnName(col))>0) {
										if (jtable != null){
											jtable.repaint();
										}
										return;
									}
								}
							}
						};//end of table listener
						((Table)vis.getGroup(group)).addTableListener(tl);
					}
				}
				updateTableUI();
			}
		};//end of GroupListener
		gListener.groupUpdated(vis.getGroups().toArray(new String[0]), null);
		vis.addGroupListener(gListener);
	}

	static boolean SHOW_SOURCE_TARGET = false;
	static boolean SHOW_ALL_FIELDS = false;
	
	protected List<String> getColumnNameCandidates() {
		Set<String> set = new HashSet<String>();
		List<Table> list = getTableList();
		for (Table table : list) {
			int i = 0;
			if (!SHOW_ALL_FIELDS && table instanceof CascadedTable) {
				i = ((CascadedTable) table).getLocalColumnCount();
			}
			for(; i<table.getColumnCount(); i++){
				set.add(table.getColumnName(i));
			}
		}
		List<String> result = new ArrayList<String>(set);
		Collections.sort(result);
		return result;
	}

	protected List<Table> getTableList() {
		List<Table> list = new ArrayList<Table>();
		for(String group : vis.getGroups()){
			TupleSet ts = vis.getGroup(group);
			if (ts instanceof Table) {
				list.add((Table) ts);
			}
		}
		return list;
	}
	
	public void setValueAt(Object aValue, Object node, int column) {
		super.setValueAt(aValue, node, column);
		vis.repaint();
	}

	protected void setFilter(Predicate filter) {
		VisNode vn = (VisNode) getRoot();
		for(int i = 0; i<vn.getChildCount(); i++){
			GroupNode gn = (GroupNode) vn.getChild(i);
			gn.setFilter(filter);
		}
		updateTableUI();
	}
}

class FieldNode extends AbstractTreeNode{
	private static ImageIcon TEXT_ICON = new ImageIcon(
			IOLib.urlFromString("field_text.png", FieldNode.class, false));
	private static ImageIcon NUMBER_ICON = new ImageIcon(
			IOLib.urlFromString("field_number.png", FieldNode.class, false));
	private static ImageIcon COMBO_ICON = new ImageIcon(
			IOLib.urlFromString("field_combo.png", FieldNode.class, false));
	private static ImageIcon BOOL_ICON = new ImageIcon(
			IOLib.urlFromString("field_bool.png", FieldNode.class, false));
	private static ImageIcon COLOR_ICON = new ImageIcon(
			IOLib.urlFromString("field_color.png", FieldNode.class, false));
	private static ImageIcon DEFAULT_ICON = new ImageIcon(
			IOLib.urlFromString("field_default.png", FieldNode.class, false));
	private static ImageIcon EDIT_ICON = new ImageIcon(
			IOLib.urlFromString("edit.png", FieldNode.class, false));
	
	public FieldNode(String labelField, String field, Tuple tuple) {
		super(labelField, field, tuple);
	}
	@Override
	public boolean nodeClicked(MouseEvent me) {
		if (me.getClickCount() == 2 ) {
			Column column = ((Tuple)obj).getTable().getColumn(str);
			Object input = null;
			String title = "Update the value in: ";
			String message = "["+str+"]:("+column.getParser().getType()+")";
			if (column.getParser() instanceof BooleanParser) {
				input = JOptionPane.showInputDialog(
						null, message, title, JOptionPane.QUESTION_MESSAGE,
						EDIT_ICON, new String[]{"true", "false"}, 
						column.getParser().format(((Tuple)obj).get(str)));
			}
			else if (column.getParser() instanceof EnumParser) {
				EnumParser eParser = (EnumParser) column.getParser();
				input = JOptionPane.showInputDialog(
						null, message, title, JOptionPane.QUESTION_MESSAGE,
						EDIT_ICON, eParser.getKeys().toArray(new String[0]), 
						eParser.format(((Tuple)obj).get(str)));
			}
			else {
				input = JOptionPane.showInputDialog(
						null, message, title, JOptionPane.QUESTION_MESSAGE, 
						EDIT_ICON, null,
						column.getParser().format(((Tuple)obj).get(str)));
			}
			if (input != null) {
				((Tuple)obj).setString(str, input.toString());
				Debug.info("Setting ["+str+"] with value = "+((Tuple)obj).get(str));
				if(obj instanceof VisualItem) {
					((VisualItem)obj).getVisualization().repaint();
				}
			}
		}
		return super.nodeClicked(me);
	}
	@Override
	public void setTreeIcon(JLabel label, boolean expanded) {
		DataParser dp = ((Tuple)obj).getTable().getColumn(str).getParser();
		if (dp instanceof ColorParser) {
			label.setIcon(COLOR_ICON);
		}
		else if (dp instanceof BooleanParser) {
			label.setIcon(BOOL_ICON);
		}
		else if (dp instanceof EnumParser) {
			label.setIcon(COMBO_ICON);
		}
		else if (((Tuple)obj).canGetDouble(str)) {
			label.setIcon(NUMBER_ICON);
		}
		else if (dp instanceof StringParser) {
			label.setIcon(TEXT_ICON);
		}
		else {
			label.setIcon(DEFAULT_ICON);	
		}
	}
	public Class getType(String field) {
		//do not have such type
		return null;
	}
	public String toString() {
		DataParser parser = ((Tuple)obj).getTable().getColumn(str).getParser();
		return "["+str+"] "+parser.format(((Tuple)obj).get(str));
	}
	public int getChildCount() {
		return 0;
	}
	public AbstractTreeNode getChild(int idx) {
		return null;
	}
	public boolean isCelleEditable(String field) {
		return false;
	}
	public Object toView(String field) {
		return null;
	}
	public boolean toData(Object view, String field) {
		return false;
	}
}//end of class FieldNode
/**
 * A node represents single tuple
 * @author weeny
 */
class TupleNode extends AbstractTreeNode{
	private static ImageIcon edgeDummyIcon = new ImageIcon(
			IOLib.urlFromString("tuple_edge_dum.png", TupleNode.class, false));
	private static ImageIcon nodeDummyIcon = new ImageIcon(
			IOLib.urlFromString("tuple_node_dum.png", TupleNode.class, false));
	private static ImageIcon nodeIcon = new ImageIcon(
			IOLib.urlFromString("tuple_node.png",     TupleNode.class, false));
	private static ImageIcon edgeIcon = new ImageIcon(
			IOLib.urlFromString("tuple_edge.png",     TupleNode.class, false));
	public MoreVisualization vis;
	
	public TupleNode(MoreVisualization vis, String labelField, 
			String group, Tuple tuple){
		super(labelField, group, tuple);
		this.vis = vis;
	}
	@Override
	public void setTreeIcon(JLabel label, boolean expanded) {
		Renderer renderer = ((VisualItem)obj).getRenderer();
		if ( obj instanceof Edge) {
			if (renderer instanceof NullRenderer) {
				label.setIcon(edgeDummyIcon);
			}
			else {
				label.setIcon(edgeIcon);
			}
		}
		else {
			if (renderer instanceof NullRenderer) {
				label.setIcon(nodeDummyIcon);
			}
			else {
				label.setIcon(nodeIcon);
			}	
		}
	}
	public String toString() {
		StringBuilder sb = new StringBuilder();
		if (((Tuple)obj).getColumnType(LABEL_FIELD) == null) {
			sb.append("(").append(((Tuple)obj).getRow()).append(" ");
			return sb.append(UNAVAILABLE).append(")").toString();
//			return UNAVAILABLE;
		}
		else {
			String result = ((Tuple)obj).getString(LABEL_FIELD);
			sb.append("(").append(((Tuple)obj).getRow());
			if (result == null) {
				return sb.append(" ").append("null").append(")").toString();
//				return "";
			}
			else {
				return sb.append(")").append(" ").append(result).toString();
//				return result;
			}
		}
	}
	@Override
	public boolean remove(boolean altDown) {
		if (vis.getGroup(str) == null){
			return false;
		}
		if (vis.getFocusGroup(str) == null && !altDown) {
			int opt = JOptionPane.showConfirmDialog(null,
				"Remove item from non-focus group?",
				"Confirm message: ", JOptionPane.YES_NO_OPTION);
			if (opt == JOptionPane.YES_OPTION) {
				altDown = true;
			}
		}
		if (vis.getFocusGroup(str) == null && altDown) {
			Debug.info("Remove item from non-focus group: "+obj);
			Tuple tp = (Tuple) obj;
			Table table = tp.getTable();
			while (table instanceof CascadedTable){
				int prow = ((CascadedTable) table).getParentRow(tp.getRow());
				table = ((CascadedTable)table).getParentTable();
				tp = table.getTuple(prow);
			}
			table.removeTuple(tp);
			return true;
		}
		else if (vis.getFocusGroup(str) == null){
			Debug.prob("Ignore removing from non-focus group: "+obj);
			return false;
		}
		else{
			Debug.info("Remove item from focus group: "+obj);
			return vis.getFocusGroup(str).removeTuple((Tuple) obj);
		}
	}
	public int getChildCount() {
		if (obj instanceof VisualItem && !PreGroupModel.SHOW_ALL_FIELDS) {
			CascadedTable ct = ((CascadedTable)((Tuple)obj).getTable());
			return ((Tuple) obj).getColumnCount() - ct.getLocalColumnCount();
		}
		return ((Tuple)obj).getColumnCount();
	}
	public AbstractTreeNode getChild(int idx) {
		idx = ((Tuple) obj).getColumnCount() - idx -1;
		final String name = ((Tuple)obj).getColumnName(idx);
		final Tuple tuple = (Tuple) this.obj;
		if (obj instanceof Edge && PreGroupModel.SHOW_SOURCE_TARGET) {
			Tuple node = null;
			if (((Edge)obj).getGraph().getEdgeSourceField().equals(name)){
				node = ((Edge)obj).getSourceNode();
			}
			else if (((Edge)obj).getGraph().getEdgeTargetField().equals(name)){
				node = ((Edge)obj).getTargetNode();
			}
			if (node != null) {
				return new TupleNode(vis, LABEL_FIELD, str, node){
					FieldNode fieldNode = new FieldNode(LABEL_FIELD, name, tuple);
					@Override
					public String toString() {
						return fieldNode.toString();
					}
					@Override
					public boolean nodeClicked(MouseEvent me) {
						if (me.getClickCount() == 2) {
							if (ModUtil.isShiftDown(me.getModifiers())) {
								fieldNode.nodeClicked(me);
								return true;
							}
						}
						return super.nodeClicked(me);
					}
				};
			}
		}
		return new FieldNode(LABEL_FIELD, name, (Tuple) obj);
	}
	public boolean isCelleEditable(String field) {
		return !(getParser(field) instanceof ObjectParser); 
	}
	public Object toView(String field) {
		if (((Tuple)obj).getColumnType(field) != null) {
			return toView(getParser(field), ((Tuple)obj).get(field));	
		}
		else {
			return UNAVAILABLE;
		}
	}
	public boolean toData(Object view, String field) {
		return toData(((Tuple)obj), view, field);
	}
	public Class getType(String field) {
		return ((Tuple)obj).getTable().getColumnType(field);
	}
	//get the parser for this field
	private DataParser getParser(String field) {
		Column col = ((Tuple)obj).getTable().getColumn(field);
		return col == null? READ_ONLY_PARSER : col.getParser();
	}
}//end of class TupleNode

/**
 * The super class of {@link VisNode} and {@link GroupNode}.
 * 
 * @author weeny
 */
abstract class ParentNode extends AbstractTreeNode{
	public ParentNode(String labelField, String str, Visualization vis) {
		super(labelField, str, vis);
	}
	public MoreVisualization getVis(){
		return (MoreVisualization) obj;
	}
	public void invalidateChildren(){
		children = null;
		valueMap = null;
		typeMap = null;
	}
	
	protected ArrayList<AbstractTreeNode> children = null;
	protected Map<String, Object> valueMap = null;
	protected Map<String, Class> typeMap = null;
	
	public AbstractTreeNode getChild(int idx) {
		if (children == null) {
			getChildCount();
		}
		return children.get(idx);
	}
	public String toString() {
		return str;
	}
	public boolean isCelleEditable(String field) {
		return getChildCount()>0;
	}
	abstract Object initValue(String field);
	public Object toView(String field) {
		if (valueMap == null) {
			valueMap = new HashMap<String, Object>();
		}
		if (!valueMap.containsKey(field)) {
			valueMap.put(field, initValue(field));
		}
		return valueMap.get(field);
	}
	public boolean toData(Object view, String field) {
		valueMap.put(field, view);
		return true;
	}
}//end of class ParentNode

/**
 * The root node, which is {@link Visualization}
 * 
 * @author weeny
 */
class VisNode extends ParentNode{
	public VisNode(String labelField, MoreVisualization vis){
		super(labelField, "All group", vis);
	}
	public int getChildCount() {
		if (children == null) {
			children = new ArrayList<AbstractTreeNode>();
			Set<String> groups = getVis().getGroups();
			for (String string : groups) {
				children.add(new GroupNode(LABEL_FIELD, string, getVis()));
			}
		}
		return children.size();
	}
	public boolean isCelleEditable(String field) {
		return false;
	}
	Object initValue(String field) {
		return "";
	}
	public Class getType(String field) {
		return String.class;
	}
}//end of class VisNode

/**
 * The node between {@link VisNode} and {@link TupleNode}, which represent a
 * group (table group, focus group, tupleset group, etc.) in the visualization.
 * 
 * @author weeny
 */
class GroupNode extends ParentNode{
	
	private Predicate filter = null;
	public static ImageIcon FOCUS_EMPTY_ICON = new ImageIcon(
			IOLib.urlFromString("focusgroup_e.png", GroupNode.class, false));
	public static ImageIcon FOCUS_FULL_ICON = new ImageIcon(
			IOLib.urlFromString("focusgroup_f.png", GroupNode.class, false));
	public static ImageIcon TABLE_ICON = new ImageIcon(
			IOLib.urlFromString("table.png",        GroupNode.class, false));
	public static ImageIcon TUPLESET_ICON = new ImageIcon(
			IOLib.urlFromString("tupleset.png",     GroupNode.class, false));
	@Override
	public void setTreeIcon(JLabel label, boolean expanded) {
		if (getVis().getFocusGroup(str)!=null) {
			if (getChildCount() == 0) {
				label.setIcon(FOCUS_EMPTY_ICON);
			}
			else {
				label.setIcon(FOCUS_FULL_ICON);
			}
		}
		else if (getVis().getGroup(str) instanceof Table){
			label.setIcon(TABLE_ICON);
		}
		else {
			label.setIcon(TUPLESET_ICON);
		}
	}
	@Override
	public boolean remove(boolean altDown) {
		if (getVis().getFocusGroup(str) == null) {
			Debug.prob("Ignore removing non-focus group!");
			return false;
		}
		boolean modified = false;
		children = null;
		if (altDown) {
			int opt = JOptionPane.showConfirmDialog(null,
					"Remove all tuples in group: "+ str,
					"Confirm message:", JOptionPane.YES_NO_OPTION);
			if (opt == JOptionPane.YES_OPTION) {
				modified = getVis().getGroup(str).getTupleCount() != 0;
				getVis().getFocusGroup(str).clear();
				Debug.info("Group tuples are removed");
			}
		}
		else{
			int opt = JOptionPane.showConfirmDialog(null,
					"Remove the whole group (including group name): "+ str,
					"Confirm message:", JOptionPane.YES_NO_OPTION);
			if (opt == JOptionPane.YES_OPTION) {
				getVis().removeGroup(str);
				Debug.info("Whole group (including group name) is removed");
				modified = true;
			}
		}
		return modified;
	}
	@Override
	public boolean dropNodes(Collection<AbstractTreeNode> coll) {
		if (getVis().getFocusGroup(str)!=null) {
			List<Tuple> list = new ArrayList<Tuple>();
			for (AbstractTreeNode node : coll) {
				if (node instanceof TupleNode) {
					list.add((Tuple) node.obj);
				}
				else if (node instanceof GroupNode){
					GroupNode gn = (GroupNode) node;
					for(int i = 0; i<gn.getChildCount(); i++){
						list.add((Tuple) gn.getChild(i).obj);
					}
				}
			}
			int cnt = 0;
			for (Tuple tuple : list) {
				if (!getVis().getFocusGroup(str).containsTuple(tuple)) {
					getVis().getFocusGroup(str).addTuple(tuple);
					cnt++;
				}
			}
			Debug.info(cnt+" tuples are added to group \""+str+"\"");
			return true;
		}
		else {
			Debug.prob("It is not a focus group, so cannot accept tuples");
		}
		return false;
	}
	public GroupNode(String labelField, String group, Visualization vis) {
		super(labelField, group, vis);
	}
	@Override
	boolean addChild(boolean altDown) {
		Visualization vis = (Visualization) obj;
		String groupName = str;
		if (vis.getGroup(groupName) != null &&
				vis.getGroup(groupName) instanceof Table) {
			Table table = (Table) vis.getGroup(groupName);
			while (table instanceof CascadedTable){
				table = ((CascadedTable)table).getParentTable();
			}
			table.addRow();
			return true;
		}
		Debug.prob(groupName + " is not a table, please select a table.");
		return false;
	}
	public void setFilter(Predicate filter){
		this.filter = filter;
		children = null;
	}
	public String toString() {
		return "["+(children == null? 0 : children.size())+"] "+str;
	}
	Object initValue(String field) {
		TupleSet ts = getVis().getGroup(str);
		for(Iterator<Tuple> it = ts.tuples(filter); it.hasNext(); ){
			Tuple tp = it.next();
			try {
				Object value = tp.get(field);
				if (value instanceof String) {
					return "";
				}
				else if (value instanceof Boolean){
					return value;
				}
				else{
					DataParser dp = tp.getTable().getColumn(field).getParser();
					return dp.format(value);
				}
			} catch (Exception e) {
				//ignore if the tuple has no such column
			}
		}
		return "";
	}
	
	public Class getType(String field) {
		TupleSet ts = getVis().getGroup(str);
		if (ts instanceof Table) {
			if (((Table)ts).getTupleCount() == 0) {
				return null;
			}
			return ((Table)ts).getColumnType(field);
		}
		else {
			if (typeMap == null) {
				typeMap = new HashMap<String, Class>();
			}
			if (!typeMap.containsKey(field)) {
				typeMap.put(field, columnType(ts.tuples(), field, true));
			}
			return typeMap.get(field);
		}
	}

	public int getChildCount() {
		if (children == null) {
			children = new ArrayList<AbstractTreeNode>();
			TupleSet ts = getVis().getGroup(str);
			if (ts == null) {
				return 0;
			}
			for(Iterator<Tuple> it = ts.tuples(filter); it.hasNext(); ){
				Tuple tp = it.next();
				children.add(new TupleNode(getVis(), LABEL_FIELD, str, tp));
			}
		}
		return children.size();
	}
	
	public boolean toData(Object view, String field) {
		TupleSet ts = getVis().getGroup(str);
		int abscenceCnt = 0;
		int modifyCnt = 0;
		int errorCnt = 0;
		if (view.equals(valueMap.get(field))) {
			Debug.prob("Ignore for the safty reason, same as previous value!");
			return false;
		}
		for(Iterator<Tuple> it = ts.tuples(filter); it.hasNext();){
			try {
				Tuple tuple = it.next();
				if (tuple.getColumnType(field)==null) {
					abscenceCnt++;
				}
				if (view instanceof String) {
					tuple.setString(field, view.toString());
					modifyCnt++;
				}
				else {
					tuple.set(field, view);
					modifyCnt++;
				}
			} catch (Exception e) {
				errorCnt++;
			}
		}
		Debug.info(ts.getTupleCount()+" in total: "+abscenceCnt+" (missing), " +
				modifyCnt+" (updated), "+errorCnt+" (errors).");
		return super.toData(view, field);
	}
	
	public static Class columnType(Iterator<Tuple> it, String field, 
			boolean allowAbsence){
		Set<Table> set = new HashSet<Table>();
		while(it.hasNext()){
			Table table = null;
			table = it.next().getTable();
			if (table != null)
				set.add(table);
		}
		Class result = null;
		for (Table table : set) {
			Class type = table.getColumnType(field);
			if (type == null && allowAbsence) {
				continue;
			}
			if (type == null) {
				return null;
			}
			else if (result == null) {
				result = type;
			}
			else if (result != type) {
				return null;
			}
		}
		return result;
	}
}//end of class GroupNode

